package com.tcm.smarthealth.service.ai;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.tcm.smarthealth.config.AiProperties;
import com.tcm.smarthealth.enums.ImageAnalysisType;
import com.tcm.smarthealth.enums.QuestionType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClientResponseException;
import reactor.core.publisher.Flux;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Duration;
import java.util.Base64;
import java.util.List;
import java.util.Map;

@Component
public class DeepSeekAiClient implements AiClient {

    private static final Logger log = LoggerFactory.getLogger(DeepSeekAiClient.class);

    private static final String MODEL = "deepseek-chat";
    private static final Duration REQUEST_TIMEOUT = Duration.ofSeconds(45);

    private final AiProperties properties;
    private final WebClient webClient;
    private final ObjectMapper objectMapper;

    public DeepSeekAiClient(AiProperties properties, WebClient.Builder builder, ObjectMapper objectMapper) {
        this.properties = properties;
        this.objectMapper = objectMapper;
        this.webClient = builder
                .baseUrl(StringUtils.hasText(properties.getBaseUrl()) ? properties.getBaseUrl() : "https://api.deepseek.com")
                .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                .defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer " + properties.getApiKey())
                .build();
    }

    @Override
    public AiInsight generateQuestionInsight(String question, QuestionType type) throws AiClientException {
        if (!properties.isEnabled() || !StringUtils.hasText(properties.getApiKey())) {
            log.warn("AI disabled or no API key, enabled={}, hasKey={}", properties.isEnabled(), StringUtils.hasText(properties.getApiKey()));
            throw new AiClientException("AI 功能未启用或缺少 API key");
        }
        String systemPrompt = "You are an expert medical consultant fluent in Traditional Chinese Medicine and Western Medicine."
                + " Respond strictly with JSON containing fields: answer (string), confidence (0-1 number), suggestions (array of strings)."
                + " Keep the language in Simplified Chinese and avoid extra text.";

        ObjectNode payload = objectMapper.createObjectNode();
        payload.put("model", MODEL);
        payload.put("temperature", 0.6);
        payload.set("reasoning", objectMapper.valueToTree(Map.of("effort", "medium")));
        payload.set("messages", objectMapper.valueToTree(List.of(
                Map.of("role", "system", "content", systemPrompt),
                Map.of("role", "user", "content", buildQuestionPrompt(question, type))
        )));

        log.debug("Sending request to DeepSeek API for question insight, type={}", type);
        String rawResponse = executeRequest(payload);
        log.debug("Received raw response from DeepSeek, length={}", rawResponse != null ? rawResponse.length() : 0);
        AiInsight result = parseInsight(rawResponse, true);
        log.info("Parsed AI insight: answer={}, suggestions={}", 
            result.getAnswer() != null ? result.getAnswer().substring(0, Math.min(50, result.getAnswer().length())) : "null",
            result.getSuggestions() != null ? result.getSuggestions().size() : 0);
        return result;
    }

    @Override
    public Flux<String> streamQuestionAnswer(String question, QuestionType type) throws AiClientException {
        if (!properties.isEnabled() || !StringUtils.hasText(properties.getApiKey())) {
            throw new AiClientException("AI 功能未启用或缺少 API key");
        }
        String systemPrompt = "You are an expert medical consultant fluent in Traditional Chinese Medicine and Western Medicine."
                + " Provide thoughtful guidance in Simplified Chinese.";

        ObjectNode payload = objectMapper.createObjectNode();
        payload.put("model", MODEL);
        payload.put("temperature", 0.6);
        payload.put("stream", true);
        payload.set("reasoning", objectMapper.valueToTree(Map.of("effort", "medium")));
        payload.set("stream_options", objectMapper.valueToTree(Map.of("include_usage", false)));
        payload.set("messages", objectMapper.valueToTree(List.of(
                Map.of("role", "system", "content", systemPrompt + " 请逐步思考并以自然段落逐条输出。"),
                Map.of("role", "user", "content", buildQuestionPrompt(question, type))
        )));

        try {
            return webClient.post()
                    .uri("/v1/chat/completions")
                    .accept(MediaType.TEXT_EVENT_STREAM)
                    .bodyValue(payload)
                    .retrieve()
                    .bodyToFlux(String.class)
                    .timeout(REQUEST_TIMEOUT)
                    .flatMap(chunk -> Flux.fromIterable(parseStreamChunks(chunk)))
                    .filter(StringUtils::hasText);
        } catch (WebClientResponseException e) {
            log.error("DeepSeek stream API error: status={}, body={}", e.getRawStatusCode(), e.getResponseBodyAsString());
            throw new AiClientException("调用 DeepSeek 流式接口失败：" + e.getRawStatusCode(), e);
        } catch (Exception e) {
            throw new AiClientException("调用 DeepSeek 流式接口异常", e);
        }
    }

    @Override
    public AiInsight analyzeImage(byte[] imageBytes, String filename, ImageAnalysisType type) throws AiClientException {
        if (!properties.isEnabled() || !StringUtils.hasText(properties.getApiKey())) {
            throw new AiClientException("AI 功能未启用或缺少 API key");
        }
        String systemPrompt = "You are an experienced medical imaging consultant."
                + " Return JSON with fields: diagnosis (string), confidence (0-1 number), suggestions (array of strings)."
                + " Respond in Simplified Chinese only.";

        ObjectNode payload = objectMapper.createObjectNode();
        payload.put("model", MODEL);
        payload.put("temperature", 0.5);
        payload.set("reasoning", objectMapper.valueToTree(Map.of("effort", "high")));
        payload.set("messages", objectMapper.valueToTree(List.of(
                Map.of("role", "system", "content", systemPrompt),
                Map.of("role", "user", "content", buildImagePrompt(imageBytes, filename, type))
        )));

        String content = executeRequest(payload);
        return parseInsight(content, false);
    }

    private String executeRequest(ObjectNode payload) throws AiClientException {
        try {
            return webClient.post()
                    .uri("/v1/chat/completions")
                    .bodyValue(payload)
                    .retrieve()
                    .bodyToMono(String.class)
                    .timeout(REQUEST_TIMEOUT)
                    .block();
        } catch (WebClientResponseException e) {
            log.error("DeepSeek API error: status={}, body={}", e.getRawStatusCode(), e.getResponseBodyAsString());
            throw new AiClientException("调用 DeepSeek 接口失败：" + e.getRawStatusCode(), e);
        } catch (Exception e) {
            throw new AiClientException("调用 DeepSeek 接口异常", e);
        }
    }

    private String buildQuestionPrompt(String question, QuestionType type) {
        return "问答类型: " + type.name() + "\n" +
                "请结合中西医视角分析以下问题，并提供专业建议。" +
                "\n问题内容：" + question;
    }

    private String buildImagePrompt(byte[] imageBytes, String filename, ImageAnalysisType type) {
        String base64 = Base64.getEncoder().encodeToString(imageBytes);
        return "影像类型: " + type.name() + "\n" +
                "文件名: " + (StringUtils.hasText(filename) ? filename : "unknown") + "\n" +
                "以下是 Base64 编码的图片数据，请结合类型给出生理意义、可能风险及调理建议。" +
                "\nIMAGE_BASE64:\n" + base64;
    }

    private AiInsight parseInsight(String rawResponse, boolean questionMode) throws AiClientException {
        try {
            log.debug("Parsing insight, questionMode={}, raw length={}", questionMode, rawResponse != null ? rawResponse.length() : 0);
            JsonNode root = objectMapper.readTree(rawResponse);
            JsonNode choices = root.path("choices");
            if (!choices.isArray() || choices.isEmpty()) {
                log.error("AI response has no choices array or is empty");
                throw new AiClientException("AI 响应为空");
            }
            JsonNode messageNode = choices.get(0).path("message");
            String content = extractMessageContent(messageNode);
            log.debug("Extracted message content length={}", content != null ? content.length() : 0);
            if (!StringUtils.hasText(content)) {
                log.error("AI message content is empty");
                throw new AiClientException("AI 未返回内容");
            }

            JsonNode payload = tryParseContent(content);
            AiInsight insight = new AiInsight();

            if (payload.hasNonNull("answer")) {
                String answer = payload.get("answer").asText();
                insight.setAnswer(answer);
                log.debug("Parsed answer field, length={}", answer.length());
            } else if (questionMode) {
                insight.setAnswer(content.trim());
                log.debug("Using raw content as answer, length={}", content.trim().length());
            }

            if (payload.hasNonNull("diagnosis")) {
                String diagnosis = payload.get("diagnosis").asText();
                insight.setDiagnosis(diagnosis);
                log.debug("Parsed diagnosis field, length={}", diagnosis.length());
            } else if (!questionMode) {
                insight.setDiagnosis(content.trim());
                log.debug("Using raw content as diagnosis");
            }

            if (payload.has("suggestions") && payload.get("suggestions").isArray()) {
                List<String> suggestions = objectMapper.convertValue(payload.get("suggestions"), objectMapper.getTypeFactory().constructCollectionType(List.class, String.class));
                insight.setSuggestions(suggestions);
                log.debug("Parsed {} suggestions", suggestions.size());
            } else {
                log.debug("No suggestions array in response");
            }

            if (payload.hasNonNull("confidence")) {
                double value = payload.get("confidence").asDouble(0.9);
                BigDecimal confidence = BigDecimal.valueOf(value).setScale(2, RoundingMode.HALF_UP);
                insight.setConfidence(confidence);
            } else {
                insight.setConfidence(BigDecimal.valueOf(0.9));
            }
            return insight;
        } catch (IOException e) {
            log.error("Failed to parse AI response", e);
            throw new AiClientException("解析 AI 响应失败", e);
        }
    }

    private JsonNode tryParseContent(String content) throws IOException {
        String trimmed = content.trim();
        if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
            return objectMapper.readTree(trimmed);
        }
        // try to locate JSON block within text
        int start = trimmed.indexOf('{');
        int end = trimmed.lastIndexOf('}');
        if (start >= 0 && end > start) {
            String jsonSegment = trimmed.substring(start, end + 1);
            return objectMapper.readTree(jsonSegment);
        }
        ObjectNode fallback = objectMapper.createObjectNode();
        fallback.put("answer", trimmed);
        return fallback;
    }

    private String extractMessageContent(JsonNode messageNode) {
        if (messageNode == null || messageNode.isMissingNode()) {
            return "";
        }
        JsonNode contentNode = messageNode.path("content");
        StringBuilder builder = new StringBuilder();

        if (contentNode.isTextual()) {
            builder.append(contentNode.asText());
        } else if (contentNode.isArray()) {
            contentNode.forEach(item -> {
                if (item == null || item.isNull()) {
                    return;
                }
                if (item.isTextual()) {
                    builder.append(item.asText());
                } else if (item.hasNonNull("text")) {
                    builder.append(item.get("text").asText());
                } else if (item.hasNonNull("content")) {
                    builder.append(item.get("content").asText());
                }
            });
        }

        if (!StringUtils.hasText(builder)) {
            JsonNode reasoning = messageNode.path("reasoning_content");
            if (reasoning.isTextual()) {
                builder.append(reasoning.asText());
            } else if (reasoning.isArray()) {
                reasoning.forEach(item -> {
                    if (item != null && !item.isNull()) {
                        if (item.isTextual()) {
                            builder.append(item.asText());
                        } else if (item.hasNonNull("text")) {
                            builder.append(item.get("text").asText());
                        }
                    }
                });
            }
        }

        return builder.toString().trim();
    }

    private List<String> parseStreamChunks(String rawChunk) {
        List<String> segments = new java.util.ArrayList<>();
        String[] lines = rawChunk.split("\\r?\\n");
        for (String line : lines) {
            String trimmed = line.trim();
            if (!trimmed.startsWith("data:")) {
                continue;
            }
            String data = trimmed.substring(5).trim();
            if (!StringUtils.hasText(data) || "[DONE]".equals(data)) {
                continue;
            }
            try {
                JsonNode node = objectMapper.readTree(data);
                JsonNode choices = node.path("choices");
                if (choices.isArray() && !choices.isEmpty()) {
                    JsonNode delta = choices.get(0).path("delta");
                    segments.addAll(extractStreamContent(delta));
                }
            } catch (Exception ex) {
                log.warn("Failed to parse stream chunk: {}", data, ex);
            }
        }
        return segments;
    }

    private List<String> extractStreamContent(JsonNode delta) {
        List<String> segments = new java.util.ArrayList<>();
        if (delta == null || delta.isMissingNode()) {
            return segments;
        }

        JsonNode reasoningNode = delta.path("reasoning_content");
        collectContentSegments(reasoningNode, segments);

        JsonNode contentNode = delta.path("content");
        collectContentSegments(contentNode, segments);

        return segments;
    }

    private void collectContentSegments(JsonNode node, List<String> segments) {
        if (node == null || node.isMissingNode() || node.isNull()) {
            return;
        }

        if (node.isTextual()) {
            String text = node.asText();
            if (StringUtils.hasText(text)) {
                segments.add(text);
            }
            return;
        }

        if (node.isArray()) {
            node.forEach(item -> {
                if (item == null || item.isNull()) {
                    return;
                }
                if (item.isTextual()) {
                    String text = item.asText();
                    if (StringUtils.hasText(text)) {
                        segments.add(text);
                    }
                } else if (item.hasNonNull("text")) {
                    String text = item.get("text").asText();
                    if (StringUtils.hasText(text)) {
                        segments.add(text);
                    }
                }
            });
        }
    }
}



